﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Geometry;

namespace RTS
{
	public class Unit : PosicionableObject, ITimeDependant
	{
		// Representation
		private const int circlePolygonizationEdges = 60;	// More result in a better movement
		private const float safeDistanceMovement = 5;
		private const float walkingStateMultiplier = 0.8F;

		public const String Idle = "idle";
		public const String Walking = "Walking";
		public const String Attack = "Attack";

		protected float trainTime;
		protected float velocity;
		protected List<Position2D> wayPointsMovement = new List<Position2D>();
		protected RTSUnitTextures unitTextures;

		public Unit(GameMain game) : base(game)
		{
			orientation = Down;
			setState(Idle);

			collisionRadius = 17;
		}

		public float getTrainTime()
		{
			return trainTime;
		}

		override public void setPosition(Position2D pos)
		{
			this.pos = pos;
			wayPointsMovement = new List<Position2D>();
		}

		public void moveTo(float x, float y)
		{
			// FALTA FIJARSE SI NOMAS INTERSECTA EN UN PUNTO
			wayPointsMovement = new List<Position2D>();
			GeomVector2 posGeomVector = new GeomVector2(pos.X, pos.Y);
			GeomVector2 endPos = new GeomVector2(x, y);

			Rect path = new Rect(posGeomVector, endPos);
			GeomVector2 directionToEnd = new GeomVector2(endPos.X - pos.X, endPos.Y - pos.Y);
			directionToEnd.Normalize();

			Dictionary<GeomVector2, Circle> collisionCircles = new Dictionary<GeomVector2, Circle>();
			List<GeomVector2> allIntersectPoints = new List<GeomVector2>();
			List<PosicionableObject> posObjects = game.getPosicionableContent();
			foreach (PosicionableObject posicionableObject in posObjects)	// Check for collisions
			{
				if (posicionableObject == this) continue;

				Position2D objectPos = posicionableObject.getPosition();
				Circle collisionCirle = new Circle(posicionableObject.getCollisionCircleCenter(), posicionableObject.CollisionRadius + collisionRadius);
				List<GeomVector2> intersectionPoints = collisionCirle.getIntersectionPoints(path);

				if (collisionCirle.containsPoint(endPos))
				{
					Circle collisionCircleWithSafeDistance = new Circle(collisionCirle.Center, collisionCirle.Radius + safeDistanceMovement);
					intersectionPoints = collisionCircleWithSafeDistance.getIntersectionPoints(path);
					if (GeomVector2.Distance(posGeomVector, intersectionPoints[0]) > GeomVector2.Distance(posGeomVector, intersectionPoints[1]))
						endPos = new GeomVector2(intersectionPoints[1]);
					else 
						endPos = new GeomVector2(intersectionPoints[0]);
				}
				else
				{
					foreach (GeomVector2 intersectPoint in intersectionPoints)
					{
						// Only add the point if is in the way to the endPoint
						if (GeomVector2.Distance(posGeomVector, endPos) > GeomVector2.Distance(posGeomVector, intersectPoint))
						{
							GeomVector2 currentDirection = new GeomVector2(intersectPoint.X - pos.X, intersectPoint.Y - pos.Y);
							currentDirection.Normalize();

							// And in the same direction 
							if (directionToEnd == currentDirection)
							{
								allIntersectPoints.Add(intersectPoint);	// Add all collisions point to a list
								collisionCircles[intersectPoint] = collisionCirle;	// Save the corresponding circle
							}
						}
					}
				}
			}

			allIntersectPoints.Sort(		// Sort the list to get the closest point to the unit first
				delegate(GeomVector2 pointA, GeomVector2 pointB) 
				{
					float distA = GeomVector2.Distance(pointA, posGeomVector);
					float distB = GeomVector2.Distance(pointB, posGeomVector);

					return distA.CompareTo(distB);
				});

			
			for (int i = 0; i < allIntersectPoints.Count; i += 2)	// 2 points correspond to the same circle
			{
				List<GeomVector2> pointsIntersectedInCircle = new List<GeomVector2>();
				pointsIntersectedInCircle.Add(allIntersectPoints[i]);
				pointsIntersectedInCircle.Add(allIntersectPoints[i + 1]);

				Circle currentPathCircle = new Circle(collisionCircles[allIntersectPoints[i]].Center, collisionCircles[allIntersectPoints[i]].Radius + safeDistanceMovement);
				List<GeomVector2> circlePolygonized = currentPathCircle.getPolygonalPoints(circlePolygonizationEdges, pointsIntersectedInCircle);
				
				if (GeomVector2.Distance(circlePolygonized[1], endPos) < GeomVector2.Distance(circlePolygonized[circlePolygonized.Count - 1], endPos))
				{
					for (int j = 0; j < circlePolygonized.Count - 1; j++)
					{
						GeomVector2 point = circlePolygonized[j];
						GeomVector2 nextPoint = circlePolygonized[j + 1];
						wayPointsMovement.Add(new Position2D(point.X, point.Y));
						if (GeomVector2.Distance(endPos, nextPoint) > GeomVector2.Distance(endPos, point)) break;
					}
				}
				else 
				{
					wayPointsMovement.Add(new Position2D(circlePolygonized[0].X, circlePolygonized[0].Y));
					for (int j = circlePolygonized.Count - 1; j > 0; j--)
					{
						GeomVector2 point = circlePolygonized[j];
						GeomVector2 nextPoint = circlePolygonized[j - 1];
						wayPointsMovement.Add(new Position2D(point.X, point.Y));
						if (GeomVector2.Distance(endPos, nextPoint) > GeomVector2.Distance(endPos, point)) break;
					}
				}
			}

			if (endPos != posGeomVector) wayPointsMovement.Add(new Position2D(endPos.X, endPos.Y));
		}


		public void stop()
		{
			wayPointsMovement = new List<Position2D>();
		}

		private void attack()
		{
			if (state == Death) return;
			if (attackTo.isDead())
			{
				setState(Idle);
				attackTo = null;
				return;
			}
			Position2D attackObjectPosition = attackTo.getPosition();
			if (attackTo == null) delayBetweenAttacksCounter = 0;
			if (attackToPrevPos != null && attackToPrevPos.Equals(attackObjectPosition) && state == Unit.Attack)
			{
				continueAttack();
				return;
			}
			
			int textureWidth = unitTextures.getAttackRangeTexture().Width;
			int textureHeight = unitTextures.getAttackRangeTexture().Height;

			int rectangleInitialPointX = (int)(pos.X - textureWidth * camera.Scale / 2 + graphPositionOffset.X);
			int rectangleInitialPointY = (int)(pos.Y - textureHeight * camera.Scale / 2 + graphPositionOffset.Y);

			bool moveToEnemy = true;

			Rectangle rectanglePosition = new Rectangle(rectangleInitialPointX, rectangleInitialPointY, (int)(textureWidth * camera.Scale), (int)(textureHeight * camera.Scale));
			
			if (rectanglePosition.Contains( new Point((int)attackObjectPosition.X, (int)attackObjectPosition.Y)))
			{
				int alpha = getAlphaInTexturePixel((int)(attackObjectPosition.X - rectangleInitialPointX), (int)(attackObjectPosition.Y - rectangleInitialPointY), unitTextures.getAttackRangeTexture());
				
				if (alpha > 0)	// In attack range
				{
					setState(Unit.Attack);
					setOrientation(new Position2D(attackObjectPosition.X - pos.X, attackObjectPosition.Y - pos.Y));
					if (wayPointsMovement.Count > 0) wayPointsMovement = new List<Position2D>();
					moveToEnemy = false;
					continueAttack();
				}

			}

			if (moveToEnemy) moveTo(attackObjectPosition.X, attackObjectPosition.Y);

			attackToPrevPos = attackObjectPosition.clone();
		}

		private void continueAttack()
		{
			if (delayBetweenAttacksCounter >= delayBetweenAttacks)
			{
				delayBetweenAttacksCounter = 0;
				attackTo.Damage(attackDamage);
			}
			attackTo.resetTimeSinceAttackCounter();
		}

		public override void update()
		{
			base.update();
			if (state == Death) return;
			if (attackTo != null) attack();
			if (wayPointsMovement.Count == 0) return;
			
			Position2D nextPos = wayPointsMovement[0];
			
			setState(Unit.Walking);
			Position2D moveVector = new Position2D(nextPos.X - pos.X, (nextPos.Y - pos.Y)); 

			//moveVector.rectificate();
			moveVector.normalize();

			// Because of the persective vertical movement must be 0.71 times slower than horizontal
			moveVector.multiplyBy(Math.Abs(moveVector.Y) * (0.71F - 1F) + 1F);

			moveVector.multiplyBy(velocity);


			if (Math.Abs(moveVector.X) + 0.5F >= Math.Abs(nextPos.X - pos.X)
				&& Math.Abs(moveVector.Y) + 0.5F >= Math.Abs(nextPos.Y - pos.Y))
			{
				move(nextPos.X - pos.X, nextPos.Y - pos.Y);
				pos = nextPos.clone();
				wayPointsMovement.RemoveAt(0);
				if (wayPointsMovement.Count == 0) setState(Unit.Idle);
			} else {
				move(moveVector.X, moveVector.Y);

				setOrientation(moveVector);
			}

		}

		private void setOrientation(Position2D moveVector)
		{
			double direction = moveVector.getDirection();

			if (Math.PI * 15 / 8 < direction || direction < Math.PI * 1 / 8) orientation = Unit.Right;
			else if (Math.PI * 1 / 8 < direction && direction < Math.PI * 3 / 8) orientation = Unit.RightDown;
			else if (Math.PI * 3 / 8 < direction && direction < Math.PI * 5 / 8) orientation = Unit.Down;
			else if (Math.PI * 5 / 8 < direction && direction < Math.PI * 7 / 8) orientation = Unit.LeftDown;
			else if (Math.PI * 7 / 8 < direction && direction < Math.PI * 9 / 8) orientation = Unit.Left;
			else if (Math.PI * 9 / 8 < direction && direction < Math.PI * 11 / 8) orientation = Unit.LeftUp;
			else if (Math.PI * 11 / 8 < direction && direction < Math.PI * 13 / 8) orientation = Unit.Up;
			else if (Math.PI * 13 / 8 < direction && direction < Math.PI * 15 / 8) orientation = Unit.RightUp;
		}

		public override void LoadContent(ContentManager Content)
		{
			textures = unitTextures;
			unitTextures.LoadContent(Content);
		}

        public override void Draw(SpriteBatch spriteBatch)
        {
			base.Draw(spriteBatch);
        }

		protected override void drawInMiddle(SpriteBatch spriteBatch)
		{
			TextureSize textureSize = unitTextures.TextureSize;

			drawObject(spriteBatch, unitTextures.GetTexture(state), unitTextures.TextureSize);
		}

		public override float getTimeElapsed()
		{
			float multiplier = (state == Walking) ? velocity * walkingStateMultiplier : 1F;

			return stateTimeElapsed * multiplier;
		}

		public override void excecuteClickAction(List<PosicionableObject> clickedObjects, MouseState mouse)
		{
			List<PosicionableObject> clickedObjectFiltered = new List<PosicionableObject>(clickedObjects);
			clickedObjectFiltered.Remove(this);

			if (mouse.LeftButton == ButtonState.Pressed)
			{
				if (clickedObjects.Contains(this))
				{
					game.selectObject(this);
				}
			}

			if (IsSelected())
			{
				if (mouse.RightButton == ButtonState.Pressed)
				{
					moveTo(mouse.X + camera.CornerPos.X, mouse.Y + camera.CornerPos.Y);
					attackTo = (clickedObjectFiltered.Count > 0) ? clickedObjectFiltered[0] : null;
				}
			}

			base.excecuteClickAction(clickedObjects, mouse);
		}

	}
}
